
#include <stdint.h>
#include "stm32f4xx.h"
#include "board.h"

#define USARTz                  USART6
#define USARTz_IRQn             USART6_IRQn

#define USARTz_Tx_DMA_Stream    DMA2_Stream7
#define USARTz_Tx_DMA_CH        DMA_Channel_5
#define USARTz_Tx_DMA_IRQn      DMA2_Stream7_IRQn
#define USARTz_Tx_DMA_FLAG      DMA_IT_TCIF7

/**
 * @brief Initialize USARTz for console
 */
int driv_console_uart_init(uint32_t baudrate)
{
    USART_InitTypeDef USART_InitStructure;
    DMA_InitTypeDef DMA_InitStructure;
    NVIC_InitTypeDef NVIC_InitStructure;

    USART_InitStructure.USART_BaudRate = baudrate;
    USART_InitStructure.USART_WordLength = USART_WordLength_8b;
    USART_InitStructure.USART_StopBits = USART_StopBits_1;
    USART_InitStructure.USART_Parity = USART_Parity_No;
    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
    USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
    USART_Init(USARTz, &USART_InitStructure);

    /* Configure DMA for USARTz TX */
    DMA_DeInit(USARTz_Tx_DMA_Stream);
    DMA_StructInit(&DMA_InitStructure);
    DMA_InitStructure.DMA_Channel = USARTz_Tx_DMA_CH;
    DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&USARTz->DR;
    DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)0;
    DMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToPeripheral;
    DMA_InitStructure.DMA_BufferSize = 1;
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
    DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
    DMA_InitStructure.DMA_Priority = DMA_Priority_Low;
    DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;
    DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull;
    DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;
    DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
    DMA_Init(USARTz_Tx_DMA_Stream, &DMA_InitStructure);
    DMA_ITConfig(USARTz_Tx_DMA_Stream, DMA_IT_TC, ENABLE);

    /* Link DMA to USARTz */
    USART_DMACmd(USARTz, USART_DMAReq_Tx, ENABLE);

    /* Configure USARTz interrupt */
    NVIC_InitStructure.NVIC_IRQChannel = USARTz_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 5;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);

    NVIC_InitStructure.NVIC_IRQChannel = USARTz_Tx_DMA_IRQn;
    NVIC_Init(&NVIC_InitStructure);

    /* Enable USARTz interrupts */
    USART_ITConfig(USARTz, USART_IT_RXNE, ENABLE);

    /* Enable USARTz */
    USART_Cmd(USARTz, ENABLE);

    return 0;
}

int driv_console_uart_write(const uint8_t* data, uint32_t len)
{
    DMA_Cmd(USARTz_Tx_DMA_Stream, DISABLE);
    DMA_ClearITPendingBit(USARTz_Tx_DMA_Stream, USARTz_Tx_DMA_FLAG);
    USARTz_Tx_DMA_Stream->M0AR = (uint32_t)data;
    USARTz_Tx_DMA_Stream->NDTR = len;
    DMA_Cmd(USARTz_Tx_DMA_Stream, ENABLE);
    return 0;
}

void USART6_IRQHandler(void)
{
    /* Handle RXNE interrupt */
    if (USART_GetITStatus(USARTz, USART_IT_RXNE) != RESET)
    {
        uint8_t data = USART_ReceiveData(USARTz);
        driv_console_uart_rx_cb(data);
        USART_ClearITPendingBit(USARTz, USART_IT_RXNE);
    }
}

void DMA2_Stream7_IRQHandler(void)
{
    /* Handle transfer complete interrupt */
    if (DMA_GetITStatus(USARTz_Tx_DMA_Stream, USARTz_Tx_DMA_FLAG) != RESET)
    {
        DMA_ClearITPendingBit(USARTz_Tx_DMA_Stream, USARTz_Tx_DMA_FLAG);
        DMA_Cmd(USARTz_Tx_DMA_Stream, DISABLE);
        driv_console_uart_tx_cb();
    }
}
